package com.joven.product.service.business.factory;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.joven.product.service.business.domain.model.entity.id.*;
import com.joven.product.service.business.domain.model.entity.Product;
import com.joven.product.service.business.domain.model.entity.ProductAttribute;
import com.joven.product.service.business.domain.model.entity.ProductDetail;
import com.joven.product.service.business.domain.model.enums.AttributeTypeEnum;
import com.joven.product.service.business.domain.model.enums.ProductStatusEnum;
import com.joven.product.service.business.domain.model.value.Attribute;
import com.joven.product.service.business.exception.ExceptionEnum;
import com.joven.product.service.client.dto.*;
import com.joven.product.service.infrastructure.converter.ProductConverter;
import com.joven.product.service.infrastructure.persistence.db.dao.ProductAttributeDao;
import com.joven.product.service.infrastructure.persistence.db.dao.ProductCategoryDao;
import com.joven.product.service.infrastructure.persistence.db.dao.ProductDao;
import com.joven.product.service.infrastructure.persistence.db.dao.ProductDetailDao;
import com.joven.product.service.infrastructure.persistence.db.entity.ProductAttributeDO;
import com.joven.product.service.infrastructure.persistence.db.entity.ProductCategoryDO;
import com.joven.product.service.infrastructure.persistence.db.entity.ProductDO;
import com.joven.product.service.infrastructure.persistence.db.entity.ProductDetailDO;
import com.joven.infra.common.model.PageResult;
import com.joven.infra.common.util.CheckUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 商品工厂
 *
 * @author Joven
 * @date 2022/6/20 10:58
 */
@Component
public class ProductFactory {
    @Autowired
    private ProductDao productDao;
    @Autowired
    private ProductDetailDao productDetailDao;
    @Autowired
    private ProductAttributeDao productAttributeDao;
    @Autowired
    private ProductCategoryDao productCategoryDao;

    public PageResult<ProductDTO> getByPage(ProductPageQueryDTO query) {
        Page<ProductDO> page = productDao.getByPage(new Page<>(query.getCurrent(), query.getSize()), ProductConverter.INSTANCE.convert(query));
        PageResult<ProductDTO> pageResult = ProductConverter.INSTANCE.convert(page);
        if (CollectionUtils.isEmpty(page.getRecords())) {
            return pageResult;
        }
        List<ProductDTO> datas = page.getRecords().stream().map(ProductConverter.INSTANCE::toProductDTO).collect(Collectors.toList());
        pageResult.setRecords(datas);
        return pageResult;
    }

    public ProductDetailDTO getDetailById(Long id) {
        ProductDO product = productDao.getById(id);
        if (product == null) {
            return null;
        }
        ProductDetailDO detailDO = productDetailDao.getByProductId(id);
        List<ProductAttributeDO> attrDOs = productAttributeDao.listByProductId(id);
        ProductDetailDTO dto = ProductConverter.INSTANCE.convert(product);
        ProductDetailDTO.ProductDetail detail = ProductConverter.ProductDetailAssembler.INSTANCE.convert(detailDO);
        List<ProductDetailDTO.ProductAttribute> attrs = ProductConverter.ProductAttributeAssembler.INSTANCE.toProductAttributes(attrDOs);
        dto.setDetail(detail);
        dto.setAttributes(attrs);
        return dto;
    }

    public Product build(ProductCreateDTO createDTO) {
        ProductCategoryDO category = productCategoryDao.getById(createDTO.getCategoryId());
        CheckUtils.assertTrue(category != null, ExceptionEnum.PRODUCT_CATEGORY_NOT_EXISTS.createException());
        Product.ProductBuilder<?, ?> productBuilder = Product.builder();
        ProductConverter.INSTANCE.update(createDTO, productBuilder);
        ProductId productId = new ProductId(EntityStatus.NEW);
        productBuilder.id(productId)
                .code(UUID.randomUUID().toString())
                .status(ProductStatusEnum.TO_SALE.getCode())
                .updateUser(createDTO.getCreateUser());
        long attrDistinctCount = createDTO.getAttributes().stream()
                .map(ProductCreateDTO.ProductAttribute::getName).distinct().count();
        CheckUtils.assertTrue(createDTO.getAttributes().size() == attrDistinctCount, ExceptionEnum.ATTR_NAME_DUPLICATE.createException());
        List<ProductAttribute> attributes = createDTO.getAttributes().stream().map(it -> {
            AttributeTypeEnum type = AttributeTypeEnum.getByCode(it.getType());
            CheckUtils.assertTrue(type != null, ExceptionEnum.ATTR_TYPE_ERROR.createException());
            Attribute attribute = ProductConverter.ProductAttributeAssembler.INSTANCE.convert(it);

            return ProductAttribute.builder()
                    .id(new ProductAttributeId(EntityStatus.NEW))
                    .productId(productId)
                    .attribute(attribute)
                    .createUser(createDTO.getCreateUser())
                    .updateUser(createDTO.getCreateUser()).build();
        }).collect(Collectors.toList());
        productBuilder.attributes(attributes);
        return productBuilder.build();
    }

    public Product build(ProductEditDTO editDTO) {
        ProductDO productDO = productDao.getById(editDTO.getId());
        CheckUtils.assertTrue(productDO != null, ExceptionEnum.PRODUCT_NOT_EXISTS.createException());

        ProductStatusEnum status = ProductStatusEnum.getByCode(editDTO.getStatus());
        CheckUtils.assertTrue(status != null, ExceptionEnum.PRODUCT_STATUS_ERROR.createException());

        ProductCategoryDO category = productCategoryDao.getById(editDTO.getCategoryId());
        CheckUtils.assertTrue(category != null, ExceptionEnum.PRODUCT_CATEGORY_NOT_EXISTS.createException());

        Product originProduct = ProductConverter.INSTANCE.toDomainObj(productDO);
        Product.ProductBuilder<?, ?> productBuilder = Product.builder();
        ProductConverter.INSTANCE.update(originProduct, productBuilder);
        ProductConverter.INSTANCE.update(editDTO, productBuilder);
        Product toEditProduct = productBuilder.build();
        if (!originProduct.equals(toEditProduct)) {
            productBuilder.id(new ProductId(productDO.getId(), EntityStatus.CHANGED))
                    .updateUser(editDTO.getUpdateUser());
        }
        ProductDetail detail = toProductDetail(editDTO);
        productBuilder.detail(detail);
        List<ProductAttribute> attrs = toProductAttributes(editDTO);
        productBuilder.attributes(attrs);
        return productBuilder.build();
    }

    private List<ProductAttribute> toProductAttributes(ProductEditDTO editDTO) {
        List<ProductAttributeDO> attributeDOList = productAttributeDao.listByProductId(editDTO.getId());
        Map<String, ProductAttributeDO> attrMap = attributeDOList.stream()
                .collect(Collectors.toMap(ProductAttributeDO::getName, Function.identity()));
        List<ProductAttribute> attrs = editDTO.getAttributes().stream().map(it -> {
            AttributeTypeEnum type = AttributeTypeEnum.getByCode(it.getType());
            CheckUtils.assertTrue(type != null, ExceptionEnum.ATTR_TYPE_ERROR.createException());

            ProductAttributeDO attr = attrMap.get(it.getName());
            ProductAttribute.ProductAttributeBuilder<?, ?> attributeBuilder = ProductAttribute.builder()
                    .productId(new ProductId(editDTO.getId(), EntityStatus.UNCHANGED));
            if (attr == null) {
                Attribute attribute = ProductConverter.ProductAttributeAssembler.INSTANCE.convert(it);
                attributeBuilder.attribute(attribute)
                        .id(new ProductAttributeId(EntityStatus.NEW))
                        .createUser(editDTO.getUpdateUser())
                        .updateUser(editDTO.getUpdateUser());
                return attributeBuilder.build();
            } else {
                Attribute attrOrigin = ProductConverter.ProductAttributeAssembler.INSTANCE.convert(attr);
                Attribute attrEdit = ProductConverter.ProductAttributeAssembler.INSTANCE.convert(it);
                if (!attrEdit.equals(attrOrigin)) {
                    attributeBuilder.id(new ProductAttributeId(attr.getId(), EntityStatus.CHANGED));
                    attributeBuilder.updateUser(editDTO.getUpdateUser());
                } else {
                    attributeBuilder.id(new ProductAttributeId(attr.getId(), EntityStatus.UNCHANGED));
                }
                attributeBuilder.attribute(attrEdit);
                attributeBuilder.version(attr.getVersion());
            }
            return attributeBuilder.build();
        }).collect(Collectors.toList());
        List<String> existedAttrs = editDTO.getAttributes().stream()
                .map(ProductEditDTO.ProductAttribute::getName).collect(Collectors.toList());
        List<ProductAttribute> deletedAttrs = attributeDOList.stream()
                .filter(it -> !existedAttrs.contains(it.getName()))
                .map(it -> ProductAttribute.builder()
                        .id(new ProductAttributeId(it.getId(), EntityStatus.DELETED)).build())
                .collect(Collectors.toList());
        attrs.addAll(deletedAttrs);
        return attrs;
    }

    private ProductDetail toProductDetail(ProductEditDTO editDTO) {
        ProductDetailDO detailDO = productDetailDao.getByProductId(editDTO.getId());
        //首次编辑可能为空
        ProductDetail.ProductDetailBuilder<?, ?> detailBuilder = ProductDetail.builder();
        detailBuilder.productId(new ProductId(editDTO.getId(), EntityStatus.CHANGED));
        if (detailDO != null) {
            ProductDetail productDetail = ProductConverter.ProductDetailAssembler.INSTANCE.toDomainObj(detailDO);

            ProductConverter.ProductDetailAssembler.INSTANCE.update(productDetail, detailBuilder);
            ProductConverter.ProductDetailAssembler.INSTANCE.update(editDTO.getDetail(), detailBuilder);
            if (!productDetail.equals(detailBuilder.build())) {
                detailBuilder.id(new ProductDetailId(detailDO.getId(), EntityStatus.CHANGED))
                        .updateUser(editDTO.getUpdateUser());
            }
        } else {
            ProductConverter.ProductDetailAssembler.INSTANCE.update(editDTO.getDetail(), detailBuilder);
            detailBuilder.id(new ProductDetailId(EntityStatus.NEW))
                    .createUser(editDTO.getUpdateUser())
                    .updateUser(editDTO.getUpdateUser());
        }
        return detailBuilder.build();
    }
}
